#include "SpaceFortress.h"
#include "OutputManager.h"

#include <assert.h>

OutputManager::OutputManager()
{
	entity_count = 0;

	// Initialize the output data.
	_data.size = 0;
	_data.capacity = 0;
	_data.buffer = NULL;

	_data.entity = NULL;
	_data.storage = NULL;
	_data.max_storage = NULL;
	_data.rate = NULL;
	_data.ratio = NULL;
	_data.comodity_index = NULL;
	_data.max_range = NULL;
	_data.min_range = NULL;
}

OutputManager::~OutputManager()
{

	// De-initialize the output data.
	if (_data.capacity) {
		delete[] _data.buffer;
	}
	_data.buffer = NULL;

	_data.entity = NULL;
	_data.storage = NULL;
	_data.max_storage = NULL;
	_data.rate = NULL;
	_data.ratio = NULL;
	_data.comodity_index = NULL;
	_data.max_range = NULL;
	_data.min_range = NULL;
}

void OutputManager::Allocate(const uint32 capacity)
{
	assert(capacity >_data.capacity);

	InstanceData new_data;
	const uint32 bytes = capacity * (sizeof(Entity) + 4 * sizeof(float) + 3 * sizeof(uint32));
	new_data.buffer = new unsigned char[bytes];
	new_data.size = _data.size;
	new_data.capacity = capacity;

	new_data.entity = (Entity*)(new_data.buffer);
	new_data.storage = (float*)(new_data.entity + capacity);
	new_data.max_storage = (float*)(new_data.storage + capacity);
	new_data.rate = (float*)(new_data.max_storage + 2 * capacity);
	new_data.ratio = (float*)(new_data.rate + capacity);
	new_data.comodity_index = (uint32*)(new_data.ratio + capacity);
	new_data.max_range = (uint32*)(new_data.comodity_index + capacity);
	new_data.min_range = (uint32*)(new_data.max_range + capacity);

	memcpy(new_data.entity, _data.entity, _data.size * sizeof(Entity));
	memcpy(new_data.storage, _data.storage, _data.size * sizeof(float));
	memcpy(new_data.max_storage, _data.max_storage, _data.size * sizeof(float));
	memcpy(new_data.rate, _data.rate, _data.size * sizeof(float));
	memcpy(new_data.ratio, _data.ratio, _data.size * sizeof(float));
	memcpy(new_data.comodity_index, _data.comodity_index, _data.size * sizeof(uint32));
	memcpy(new_data.max_range, _data.max_range, _data.size * sizeof(uint32));
	memcpy(new_data.min_range, _data.min_range, _data.size * sizeof(uint32));

	delete[] _data.buffer;
	_data = new_data;
}

void OutputManager::SortData(const uint32 start, const uint32 stop)
{
	int32 i = start, j = stop;
	uint32 pivot = _data.entity[(start + stop) / 2].index();

	while (i <= j) {
		while (_data.entity[i].index() < pivot) {
			i++;
		}
		while (_data.entity[j].index() > pivot) {
			j--;
		}
		if (i <= j) {
			Entity temp_entity = _data.entity[i];
			float temp_storage = _data.storage[i];
			float temp_max_storage = _data.max_storage[i];
			float temp_rate = _data.rate[i];
			float temp_ratio = _data.ratio[i];
			uint32 temp_comodity_index = _data.comodity_index[i];
			uint32 temp_max_range = _data.max_range[i];
			uint32 temp_min_range = _data.min_range[i];

			_data.entity[i] = _data.entity[j];
			_data.storage[i] = _data.storage[j];
			_data.max_storage[i] = _data.max_storage[j];
			_data.rate[i] = _data.rate[j];
			_data.ratio[i] = _data.ratio[j];
			_data.comodity_index[i] = _data.comodity_index[j];
			_data.max_range[i] = _data.max_range[j];
			_data.min_range[i] = _data.min_range[j];

			_data.entity[j] = temp_entity;
			_data.storage[j] = temp_storage;
			_data.max_storage[j] = temp_max_storage;
			_data.rate[j] = temp_rate;
			_data.ratio[j] = temp_ratio;
			_data.comodity_index[j] = temp_comodity_index;
			_data.max_range[j] = temp_max_range;
			_data.min_range[j] = temp_min_range;

			i++;
			j--;
		}
	}

	if (start < j) {
		SortData(start, j);
	}
	if (i < stop) {
		SortData(i, stop);
	}
}

void OutputManager::CalculateOutputRatio()
{
	// Calculate the ratios.
	for (uint32 i = 0; i < _data.size; i++) {
		float ratio = (_data.max_storage[i] - _data.storage[i]) / _data.rate[i];
		_data.ratio[i] = (ratio > 1.f) ? 1.f : ratio;
	}
}

void OutputManager::InitializeProduction(Entity* entities, float* production)
{
	entities = new Entity[entity_count];
	production = new float[entity_count];

	for (uint32 i = 1; i < entity_count; i++) {
		production[i] = 1.f;
	}

	uint32 entity_idx = 0;
	Entity last_entity = _data.entity[0];
	entities[0] = _data.entity[0];
	production[0] = _data.ratio[0];
	for (uint32 i = 1; i < _data.size; i++) {
		if (last_entity != _data.entity[i]) {
			last_entity = _data.entity[i];
			entity_idx++;

			entities[entity_idx] = _data.entity[i];
		}

		if (production[entity_idx] > _data.ratio[i]) {
			production[entity_idx] = _data.ratio[i];
		}
	}
}

void OutputManager::OverrideRatios(const uint32 entity_count, Entity* entities, float* production)
{
	uint32 i = 0, j = 0;
	while (i < _data.size && j < entity_count) {
		if (_data.entity[i].index() < entities[j].index()) {
			i++;
		} else if (_data.entity[i].index() > entities[j].index()) {
			j++;
		} else { // equal
			_data.ratio[i] = production[j];
		}
	}
}

void OutputManager::ProduceOutput()
{
	for (uint32 i = 0; i < _data.size; i++) {
		float new_amount = _data.ratio[i] * _data.rate[i];
		_data.storage[i] += new_amount;
	}
}

void OutputManager::PlaceMarketSells(BidData* bids) const
{
	for (uint32 i = 0; i < _data.size; i++) {
		const int32 comodity_idx = _data.comodity_index[i];
		BidData& bd = bids[comodity_idx];
		const uint32 idx = bd.size++;

		uint32 range = _data.max_range[i] - _data.min_range[i];
		float favorability = (float)(bd.price[idx] - _data.max_range[i]);
		favorability /= (float)range;

		bd.entity[idx] = _data.entity[i];
		bd.quantity[idx] = _data.storage[i] * favorability;
		bd.price[idx] = rand() % range + _data.min_range[i];
		bd.storage_index[idx] = i;
	}
}

void OutputManager::RegisterSellResults(const BidData* const bids, const float* const comodity_mean, const float* const comodity_supply, const float* const comodity_demand, const uint32 num_resources)
{
	for (uint32 resource = 0; resource < num_resources; resource++) {
		const BidData& bd = bids[resource];

		for (uint32 i = 0; i < bd.size; i++) {
			const uint32 idx = bd.storage_index[i];

			if (bd.quantity_traded[i]) {
				_data.storage[idx] -= bd.quantity_traded[i];

				const float mean_price = comodity_mean[_data.comodity_index[idx]];
				const int32 avg_price = bd.clearing_price[i] / bd.quantity_traded[i];

				if (avg_price > _data.max_range[idx]) {
					_data.max_range[idx] = avg_price;
				}
				else if (avg_price < _data.min_range[idx]) {
					_data.min_range[idx] = avg_price;
				}
				else {
					//Tighten the ranges around the mean by 5%
					_data.max_range[idx] -= (_data.max_range[idx] - mean_price) * 0.05f;
					_data.min_range[idx] += (mean_price - _data.min_range[idx]) * 0.05f;
				}
			} else {
				// failed to complete trade order.
				// If output is 90% full sell at a much lower price
				if (_data.storage[idx] / _data.max_storage[idx] > 0.9f) {
					_data.max_range[idx] *= 0.5f; // Decrease maximum bound by 50%
					_data.min_range[idx] *= 0.5f; // Decrease minimum bound by 50%
					continue;
				} // else
				// look at supply and demand and try to predict future trends.
				const float sd_ratio = comodity_demand[i] / comodity_supply[i];
				_data.max_range[idx] *= sd_ratio;
				_data.min_range[idx] *= sd_ratio;
			}
		}
	}
}

// Note that this requires the input array of defunct_entities to be sorted ascending.
// This may run into problems if its instructed to remove an entity it doesn't have. - although that should never happen.
void OutputManager::RemoveOutputData(const OpenVector<Entity>& defunct_entities, OpenVector<uint32>& defunct_sellers)
{
	const uint32 entity_count = defunct_entities.size;
	uint32 shift_count = 0;
	uint32 i = 0, j = 0;

	// It may be worth while to match indexes to the first defunct entity. - binary search the _data.entities for the first defunct.
	while (i < _data.size && j < entity_count) {
		if (_data.entity[i].index() < defunct_entities.data[j].index()) {

			_data.entity[i - shift_count] = _data.entity[i];
			_data.storage[i - shift_count] = _data.storage[i];
			_data.max_storage[i - shift_count] = _data.max_storage[i];
			_data.rate[i - shift_count] = _data.rate[i];
			_data.ratio[i - shift_count] = _data.ratio[i];
			_data.comodity_index[i - shift_count] = _data.comodity_index[i];
			_data.max_range[i - shift_count] = _data.max_range[i];
			_data.min_range[i - shift_count] = _data.min_range[i];

			i++;
		}
		else if (_data.entity[i].index() > defunct_entities.data[j].index()) {
			j++;
		}
		else { // equal.
			defunct_sellers.Add(_data.comodity_index[i]);

			shift_count++;
			i++;
		}
	}

	_data.size -= entity_count;
}

void OutputManager::AllocateStorage(const Entity* const entities, const uint32 num_entities, const uint32 input_cost,
	const uint32* const comodity_indexes, const float* const comodity_rates, const uint32 num_outputs, const float* const mean_prices)
{
	if (_data.size + num_outputs * num_entities) {
		Allocate(_data.size + num_outputs * num_entities);
	}

	for (uint32 i = 0; i < num_entities; i++) {
		for (uint32 j = 0; j < num_outputs; j++) {
			const uint32 idx = _data.size++;

			_data.entity[idx] = entities[i];
			_data.storage[idx] = 0.f;
			_data.max_storage[idx] = 20.f; // Arbitrary value.
			_data.rate[idx] = comodity_rates[j];
			_data.ratio[idx] = 0.f; // This gets re-calculated every tick.

			const uint32 com_idx = comodity_indexes[j];
			_data.comodity_index[idx] = com_idx;

			// Labor costs need to be factored in to this.
			float mean = mean_prices[com_idx];
			float per_output_cost = 1.3 * input_cost / num_outputs;
			if (mean < per_output_cost) {
				mean = per_output_cost;
			}

			// 60% range centered around the mean.
			_data.max_range[idx] = 1.3 * mean;
			_data.min_range[idx] = 0.7 * mean;
		}
	}

	// quicksort probably isnt the best option since the array is already mostly sorted.
	SortData(0, _data.size);
}